﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using BLL.Workflows;
using DO.Workflows;

namespace BLL.Workers
{
    /// <summary>
    /// job,run,input,output stored in memory
    /// no interaction to database
    /// single-thread, synchronous
    /// </summary>
    public class ManualWorkflowAgent
    {
        private readonly WorkflowDesign _WfDesign;

        public ManualWorkflowAgent(WorkflowDesign wfDesign)
        {
            this._WfDesign = wfDesign;
        }

        public Dictionary<Guid, ResultBase> Run()
        {
            Dictionary<Guid, ResultBase> outputs = new Dictionary<Guid, ResultBase>();
            int iterationCount = 0;
            while (outputs.Count < this._WfDesign.Components.Count)
            {
                iterationCount++;
                foreach (WorkflowComponentBase wfComponent in this._WfDesign.Components.Values)
                {
                    if (outputs.ContainsKey(wfComponent.ComponentID))
                        continue;
                    if (this.CanStart(wfComponent, outputs))
                    {
                        InstructionBase instruction = this.ComputeInstructionForJob(_WfDesign, wfComponent, outputs);
                        wfComponent.Instruction = instruction;
                        ResultBase output = this.StartJob(wfComponent);
                        outputs.Add(wfComponent.ComponentID, output);
                        break;
                    }
                }
                Thread.Sleep(5000);

                if(iterationCount >=this._WfDesign.Components.Count)
                {
                    break;
                }
            }
            return outputs;
        }

        private bool CanStart(WorkflowComponentBase component, Dictionary<Guid, ResultBase> outputs)
        {
            if (component.Dependencies == null || component.Dependencies.Count == 0)
                return true;
            int finishedDependentJobs = 0;
            foreach (Guid dependentComponentID in component.Dependencies)
            {
                if (outputs.ContainsKey(dependentComponentID))
                    finishedDependentJobs++;
            }
            if (finishedDependentJobs == component.Dependencies.Count)
                return true;
            else
                return false;
        }

        private ResultBase StartJob(WorkflowComponentBase wfComponent)
        {
            string componentName = wfComponent.GetType().Name;
            if (ManualWorkersRegister.Workers != null && ManualWorkersRegister.Workers.ContainsKey(componentName))
            {
                IWorker worker = ManualWorkersRegister.Workers[componentName];
                worker.SetInstruction(
                    Guid.NewGuid(), wfComponent.Instruction.Serialize().ToString(), 
                    this._WfDesign.WorkflowName, wfComponent.ComponentName);
                worker.DoWork();
                return worker.Output;
            }
            else 
                throw new Exception("Unable to handle work for "+componentName);
        }


        #region instructions
        private InstructionBase ComputeInstructionForJob(
            WorkflowDesign wfDesign, WorkflowComponentBase wfComponent,
            Dictionary<Guid, ResultBase> outputs)
        {
            InstructionBase instruction = wfComponent.Instruction;
            List<WorkflowComponentBase> upstreamComponents = new List<WorkflowComponentBase>();
            if (wfDesign.SuccessConnectors != null && wfDesign.SuccessConnectors.Count > 0)
            {
                foreach (Guid fromID in wfDesign.SuccessConnectors.Keys)
                {
                    if (wfDesign.SuccessConnectors[fromID].Contains(wfComponent.ComponentID))
                    {
                        WorkflowComponentBase upstreamComponent = wfDesign.Components[fromID];
                        if (!upstreamComponents.Contains(upstreamComponent))
                            upstreamComponents.Add(upstreamComponent);
                    }
                }
            }
            if (wfDesign.FailureConnectors != null && wfDesign.FailureConnectors.Count > 0)
            {
                foreach (Guid fromID in wfDesign.FailureConnectors.Keys)
                {
                    if (wfDesign.FailureConnectors[fromID].Contains(wfComponent.ComponentID))
                    {
                        WorkflowComponentBase upstreamComponent = wfDesign.Components[fromID];
                        if (!upstreamComponents.Contains(upstreamComponent))
                            upstreamComponents.Add(upstreamComponent);
                    }
                }
            }

            if (instruction != null)
            {
                if (upstreamComponents.Count > 0)
                {
                    foreach (WorkflowComponentBase upstreamComponent in upstreamComponents)
                    {
                        if (wfDesign.DataPipeConverters != null && 
                            wfDesign.DataPipeConverters.ContainsKey(upstreamComponent.ComponentID) &&
                            outputs.ContainsKey(upstreamComponent.ComponentID) && 
                            wfDesign.DataPipeConverters[upstreamComponent.ComponentID].ContainsKey(wfComponent.ComponentID))
                        {
                            ResultBase jobOutput = outputs[upstreamComponent.ComponentID];
                            List<CodeFieldMapper> mappers =
                                wfDesign.DataPipeConverters[upstreamComponent.ComponentID][wfComponent.ComponentID];
                            WorkflowManager.UpdateJobInstruction(ref instruction, jobOutput, mappers.ToArray());
                        }
                    }
                }
                return instruction;
            }
            else
            {
                return null;
            }
        }
        #endregion
    }
}
